﻿using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using PenguinForce.FlockBehaviors;
using PenguinForce.Collisions;
using System.Collections.Generic;
using Microsoft.Xna.Framework.Content;


namespace PenguinForce.Entities
{
    public class Penguin : Entity, ICollisionEntity
    {
        #region Constants

        private const float DEFAULT_MOVEMENT_SPEED = 100.0f;

        #endregion

        #region Variables

        protected Random _random;
        private Vector2 _aiNewDir;
        private int _aiNumSeen;

        private SpriteSheet _sprite;

        #endregion

        #region Constructors

        /// <summary>
        /// Bird constructor
        /// </summary>
        /// <param name="theTex"></param>
        /// <param name="theDir">movement direction</param>
        /// <param name="theLoc">spawn location</param>
        /// <param name="screenSize">screen size</param>
        public Penguin(ContentManager content, Vector2 theDir, Vector2 theLoc)
            : base(null)
        {
            _direction = theDir;
            _direction.Normalize();
            _location = theLoc;
            _moveSpeed = DEFAULT_MOVEMENT_SPEED;
            _fleeing = false;
            _random = new Random((int)theLoc.X + (int)theLoc.Y);
            _animaltype = EntityType.Bird;
            BuildBehaviors();

            LoadContent(content);
        }

        #endregion

        #region Properties

        public BoundingSphere Bounds
        {
            get
            {
                return new BoundingSphere(new Vector3(this._location, 0.0f), 16.0f);
            }
        }

        public Dictionary<EntityType, FlockBehaviors.Behaviors> Behaviors
        {
            get
            {
                return _behaviors;
            }
        }

        #endregion

        #region Methods

        private void LoadContent(ContentManager content)
        {
            _sprite = content.Load<SpriteSheet>("PenguinSprite");
            _texture = _sprite.Texture;

            Rectangle src = _sprite.SourceRectangle(0);
            _textureCenter = new Vector2(src.Width / 2, src.Height / 2);
        }

        /// <summary>
        /// update bird position, wrapping around the screen edges
        /// </summary>
        /// <param name="gameTime"></param>
        public void Update(GameTime gameTime, ref AIParameters aiParams)
        {
            UpdateAnimation(gameTime);

            float elapsedTime = (float)gameTime.ElapsedGameTime.TotalSeconds;
            Vector2 randomDir = Vector2.Zero;

            randomDir.X = (float)_random.NextDouble() - 0.5f;
            randomDir.Y = (float)_random.NextDouble() - 0.5f;
            Vector2.Normalize(ref randomDir, out randomDir);

            if (_aiNumSeen > 0)
            {
                _aiNewDir = (_direction * aiParams.moveInOldDirInfluence) + (_aiNewDir * (aiParams.moveInFlockDirInfluence / (float)_aiNumSeen));
            }
            else
            {
                _aiNewDir = _direction * aiParams.moveInOldDirInfluence;
            }

            _aiNewDir += (randomDir * aiParams.moveInRandomDirInfluence);
            Vector2.Normalize(ref _aiNewDir, out _aiNewDir);
            _aiNewDir = ChangeDirection(_direction, _aiNewDir, aiParams.maxTurnRadians * elapsedTime);
            _direction = _aiNewDir;

            if (_direction.LengthSquared() > 0.01f)
            {
                Vector2 moveAmount = _direction * _moveSpeed * elapsedTime * CollisionManager.Instance.GetCurrentLevel().GetFriction(_location);
                _location += moveAmount;
                if (CollisionManager.Instance.ContainsHole(this)) {
                    CollisionManager.Instance.Penguins.Kill(this);

                } else if (CollisionManager.Instance.ContainsBarrier(this))
                {
                    _location -= moveAmount;
                }
                else
                {
                    BoundingBox? thinIceBox = CollisionManager.Instance.ContainsThinIce(this);
                    if (thinIceBox != null)
                    {
                        CollisionManager.Instance.WeakenIce((BoundingBox)thinIceBox);

                    }

                    if (CollisionManager.Instance.ContainsEntity(this))
                    {
                        _location -= moveAmount;
                        if (CollisionManager.Instance.ContainsEntity(this))
                        {
                            _location += moveAmount;
                        }
                    }
                }
                CollisionManager.Instance._stepController.AddPenguinStep(gameTime, _location, _direction);
                CollisionManager.Instance._stepController.Update(gameTime);
            }

        }

        private void UpdateAnimation(GameTime gameTime)
        {
        }

        /// <summary>
        /// Draw the bird, tinting it if it's currently fleeing
        /// </summary>
        /// <param name="spriteBatch"></param>
        /// <param name="gameTime"></param>
        public override void Draw(SpriteBatch spriteBatch, GameTime gameTime)
        {
            float rotation = (float)Math.Atan2(_direction.Y, _direction.X);

            int spriteIndex = (int)(gameTime.TotalGameTime.TotalMilliseconds / 100) % this._sprite.Count;
            Rectangle src = _sprite.SourceRectangle(spriteIndex);
            _textureCenter = new Vector2(src.Width / 2, src.Height / 2);

            // Draw the animal, centered around its position, and using the orientation and tint color.
            spriteBatch.Draw(_sprite.Texture, _location, src, Color.White, rotation, _textureCenter, 1.0f, SpriteEffects.None, 0.0f);
        }

        /// <summary>
        /// Instantiates all the behaviors that this Bird knows about
        /// </summary>
        public void BuildBehaviors()
        {
            FlockBehaviors.Behaviors polarBearReactions = new FlockBehaviors.Behaviors();
            polarBearReactions.Add(new FleeBehavior(this));
            _behaviors.Add(EntityType.PolarBear, polarBearReactions);


            FlockBehaviors.Behaviors tankReactions = new FlockBehaviors.Behaviors();
            tankReactions.Add(new SeekBehavior(this));
            _behaviors.Add(EntityType.Tank, tankReactions);


            FlockBehaviors.Behaviors birdReactions = new FlockBehaviors.Behaviors();
            birdReactions.Add(new AlignBehavior(this));
            birdReactions.Add(new CohesionBehavior(this));
            birdReactions.Add(new SeparationBehavior(this));
            _behaviors.Add(EntityType.Bird, birdReactions);
            
        }

        /// <summary>
        /// Setup the bird to figure out it's new movement direction
        /// </summary>
        /// <param name="AIparams">flock AI parameters</param>
        public void ResetThink()
        {
            Fleeing = false;
            _aiNewDir = Vector2.Zero;
            _aiNumSeen = 0;
            _reactionDistance = 0f;
            _reactionLocation = Vector2.Zero;
        }

        /// <summary>
        /// React to an Animal based on it's type
        /// </summary>
        /// <param name="theAnimal"></param>
        public void ReactTo(Entity theAnimal, ref AIParameters AIparams)
        {
            if (theAnimal != null)
            {
                //setting the the reactionLocation and reactionDistance here is
                //an optimization, many of the possible reactions use the distance
                //and location of theAnimal, so we might as well figure them out
                //only once !
                _reactionLocation = theAnimal.Location;
                //reactionDistance = Vector2.Distance(location, reactionLocation);
                _reactionDistance = Math.Abs(Vector2.Distance(_location, _reactionLocation));

                //we only react if theAnimal is close enough that we can see it
                if ((_reactionDistance < AIparams.detectionDist) || (theAnimal is FlockMaster))
                {
                    FlockBehaviors.Behaviors reactions = _behaviors[theAnimal.AnimalType];
                    foreach (Behavior reaction in reactions)
                    {
                        reaction.Update(theAnimal, AIparams);
                        if (reaction.Reacted)
                        {
                            _aiNewDir += reaction.Reaction;
                            _aiNumSeen++;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// This function clamps turn rates to no more than maxTurnRadians
        /// </summary>
        /// <param name="oldDir">current movement direction</param>
        /// <param name="newDir">desired movement direction</param>
        /// <param name="maxTurnRadians">max turn in radians</param>
        /// <returns></returns>
        private static Vector2 ChangeDirection(Vector2 oldDir, Vector2 newDir, float maxTurnRadians)
        {
            float oldAngle = (float)Math.Atan2(oldDir.Y, oldDir.X);
            float desiredAngle = (float)Math.Atan2(newDir.Y, newDir.X);
            //float newAngle = MathHelper.Clamp(desiredAngle, WrapAngle(-maxTurnRadians), WrapAngle(maxTurnRadians));
            float newAngle = MathHelper.Clamp(desiredAngle, WrapAngle(oldAngle - maxTurnRadians), WrapAngle(oldAngle + maxTurnRadians));
            return new Vector2((float)Math.Cos(newAngle), (float)Math.Sin(newAngle));
        }

        /// <summary>
        /// clamps the angle in radians between -Pi and Pi.
        /// </summary>
        /// <param name="radians"></param>
        /// <returns></returns>
        private static float WrapAngle(float radians)
        {
            while (radians < -MathHelper.Pi)
            {
                radians += MathHelper.TwoPi;
            }
            while (radians > MathHelper.Pi)
            {
                radians -= MathHelper.TwoPi;
            }
            return radians;
        }

        #endregion
    }  
}